package com.shxzz.shortlink.project.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shxzz.shortlink.project.common.convention.exception.ClientException;
import com.shxzz.shortlink.project.common.convention.exception.ServiceException;
import com.shxzz.shortlink.project.common.enums.ValidDateType;
import com.shxzz.shortlink.project.config.AmapProperties;
import com.shxzz.shortlink.project.dao.entity.*;
import com.shxzz.shortlink.project.dao.mapper.*;
import com.shxzz.shortlink.project.dto.biz.LinkAccessInfoDTO;
import com.shxzz.shortlink.project.dto.req.LinkCreateReqDTO;
import com.shxzz.shortlink.project.dto.req.LinkPageReqDTO;
import com.shxzz.shortlink.project.dto.req.LinkUpdateReqDTO;
import com.shxzz.shortlink.project.dto.resp.LinkCreateRespDTO;
import com.shxzz.shortlink.project.dto.resp.LinkGroupCountRespDTO;
import com.shxzz.shortlink.project.dto.resp.LinkPageRespDTO;
import com.shxzz.shortlink.project.service.LinkService;
import com.shxzz.shortlink.project.service.UrlInfoService;
import com.shxzz.shortlink.project.utils.HashUtil;
import com.shxzz.shortlink.project.utils.LinkUtil;
import com.shxzz.shortlink.project.utils.RequestInfoUtil;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import static com.shxzz.shortlink.project.common.constant.RedisCacheConstant.*;

/**
 * @author shxzz
 * @create 2023-12-17-16:16
 * @description 短链接接口实现
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class LinkServiceImpl extends ServiceImpl<LinkMapper, LinkDO> implements LinkService {

    private final LinkMapper linkMapper;

    private final LinkGotoMapper linkGotoMapper;

    private final LinkStatusTodayMapper linkStatusTodayMapper;

    private final LinkLocaleStatusMapper linkLocaleStatusMapper;

    private final LinkDeviceStatusMapper linkDeviceStatusMapper;

    private final LinkBrowserStatusMapper linkBrowserStatusMapper;

    private final LinkNetworkStatusMapper linkNetworkStatusMapper;

    private final LinkOsStatusMapper linkOsStatusMapper;

    private final LinkAccessLogMapper linkAccessLogMapper;

    private final LinkAccessStatusMapper linkAccessStatusMapper;

    private final UrlInfoService urlInfoService;

    private final RBloomFilter<String> linkGenerateCachePenetration;

    private final RedissonClient redissonClient;

    private final StringRedisTemplate stringRedisTemplate;

    private final AmapProperties amapProperties;

    private static final int LINK_GENERATE_MAX_COUNT = 10;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public LinkCreateRespDTO createLink(LinkCreateReqDTO requireParam) {

        String linkSuffix = generateShortLinkSuffix(requireParam.getDomain(), requireParam.getOriginUrl());

        UrlBuilder urlBuilder = UrlBuilder.of()
                .setHost(requireParam.getDomain())
                .addPath(linkSuffix);
        String fullShortLink = urlBuilder.getHost() + urlBuilder.getPath();

        String faviconUrl = "";
        try {
            faviconUrl = urlInfoService.getTargetUrlFavicon(requireParam.getOriginUrl());
        } catch (Exception e) {
            log.warn("创建短链接获取网站：{} 网址图标失败", requireParam.getOriginUrl());
        }

        LinkDO linkDO = LinkDO.builder()
                .originUrl(requireParam.getOriginUrl())
                .describe(requireParam.getDescribe())
                .validDateType(requireParam.getValidDateType())
                .validDate(requireParam.getValidDate())
                .shortUri(linkSuffix)
                .domain(requireParam.getDomain())
                .fullShortUrl(fullShortLink)
                .gid(requireParam.getGid())
                .createdType(requireParam.getCreatedType())
                .favicon(faviconUrl)
                .enableStatus(0)
                .totalPv(0)
                .totalUv(0)
                .totalUip(0)
                .build();

        LinkGotoDO linkGotoDO = LinkGotoDO.builder()
                .fullShortUrl(fullShortLink)
                .gid(requireParam.getGid())
                .build();

        try {
            int insert = linkMapper.insert(linkDO);
            if (insert != 1) {
                throw new ServiceException("短链接插入数据库失败");
            }

            linkGotoMapper.insert(linkGotoDO);
        } catch (DuplicateKeyException e) {
            throw new ServiceException("短链接插入数据库重复");
        }

        linkGenerateCachePenetration.add(fullShortLink);

        // 缓存预热
        LinkGotoDO linkGotoCache = LinkGotoDO.builder()
                .targetUrl(linkDO.getOriginUrl())
                .gid(requireParam.getGid())
                .fullShortUrl(fullShortLink)
                .build();
        stringRedisTemplate.opsForValue().set(
                LINK_GOTO_KEY + fullShortLink,
                JSON.toJSONString(linkGotoCache),
                LinkUtil.getCacheExpireTime(linkDO.getValidDate()),
                TimeUnit.MILLISECONDS);

        return LinkCreateRespDTO.builder()
                .fullShortUrl("http://" + fullShortLink)
                .gid(linkDO.getGid())
                .originUrl(linkDO.getOriginUrl())
                .build();

    }

    @Override
    public IPage<LinkPageRespDTO> selectPageLink(LinkPageReqDTO requireParam) {
        IPage<LinkDO> page = linkMapper.selectPageLink(requireParam);
        return page.convert(linkDO -> BeanUtil.toBean(linkDO, LinkPageRespDTO.class));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateLink(LinkUpdateReqDTO requireParam) {
        if (requireParam.getGid() != null) {
            LambdaQueryWrapper<LinkDO> queryWrapper = Wrappers.lambdaQuery(LinkDO.class)
                    .eq(LinkDO::getGid, requireParam.getOriginGid())
                    .eq(LinkDO::getFullShortUrl, requireParam.getFullShortUrl())
                    .eq(LinkDO::getEnableStatus, 0)
                    .eq(LinkDO::getDelFlag, 0);

            LinkDO linkDOInBase = linkMapper.selectOne(queryWrapper);

            if (linkDOInBase == null) {
                throw new ClientException("该短链接不存在");
            }

            linkMapper.delete(queryWrapper);

            linkDOInBase.setGid(requireParam.getGid());
            linkDOInBase.setId(null);
            linkMapper.insert(linkDOInBase);

        }

        LambdaUpdateWrapper<LinkDO> updateWrapper = Wrappers.lambdaUpdate(LinkDO.class)
                .eq(CharSequenceUtil.isNotEmpty(requireParam.getGid()), LinkDO::getGid, requireParam.getGid())
                .eq(CharSequenceUtil.isEmpty(requireParam.getGid()), LinkDO::getGid, requireParam.getOriginGid())
                .eq(LinkDO::getFullShortUrl, requireParam.getFullShortUrl())
                .eq(LinkDO::getEnableStatus, 0)
                .eq(LinkDO::getDelFlag, 0)
                .set(Objects.equals(requireParam.getValidDateType(), ValidDateType.PERMANENT.getType()),
                        LinkDO::getValidDate, null)
                .set(Objects.equals(requireParam.getValidDateType(), ValidDateType.CUSTOM.getType()),
                        LinkDO::getValidDate, requireParam.getValidDate());


        LinkDO linkDO = LinkDO.builder()
                .originUrl(requireParam.getOriginUrl())
                .describe(requireParam.getDescribe())
                .validDateType(requireParam.getValidDateType())
                .build();

        linkMapper.update(linkDO, updateWrapper);
    }

    @Override
    public List<LinkGroupCountRespDTO> selectLinkGroupCount(List<String> gids) {
        List<LinkGroupCountRespDTO> linkGroupCountList = linkMapper.selectLinkGroupCount(gids);
        return gids.stream().map(gid -> {
            LinkGroupCountRespDTO linkGroupCountRespDTO = new LinkGroupCountRespDTO();
            linkGroupCountRespDTO.setLinkCount(0);
            linkGroupCountRespDTO.setGid(gid);
            linkGroupCountList.stream()
                    .filter(linkGroupCount -> linkGroupCount.getGid().equals(gid))
                    .findFirst()
                    .ifPresent(linkGroupCount -> linkGroupCountRespDTO.setLinkCount(linkGroupCount.getLinkCount()));
            return linkGroupCountRespDTO;
        }).toList();
    }

    @Override
    public void restoreUrl(String shortUri, HttpServletRequest request, HttpServletResponse response) {
        String fullShortLink = request.getServerName() + "/" + shortUri;

        // 查询缓存
        LinkGotoDO linkGotoDO = JSON.parseObject(stringRedisTemplate.opsForValue().get(LINK_GOTO_KEY + fullShortLink), LinkGotoDO.class);
        if (linkGotoDO != null) {
            // 记录访问信息
            LinkAccessInfoDTO linkAccessInfoDTO = buildLinkAccessInfoDTO(fullShortLink, linkGotoDO.getGid(), request, response);
            saveLinkAccessInfo(linkAccessInfoDTO);
            redirectToTargetUrl(linkGotoDO.getTargetUrl(), response);
            return;
        }

        if (!linkGenerateCachePenetration.contains(fullShortLink)) {
            redirectToNotFound(response);
            return;
        }

        // 查询空缓存
        String gotoIsNull = stringRedisTemplate.opsForValue().get(LINK_GOTO_NULL_KEY + fullShortLink);
        if (CharSequenceUtil.isNotEmpty(gotoIsNull)) {
            redirectToNotFound(response);
            return;
        }

        RLock linkGotoLock = redissonClient.getLock(LOCK_LINK_GOTO_SELECT + fullShortLink);
        linkGotoLock.lock();
        try {
            linkGotoDO = JSON.parseObject(stringRedisTemplate.opsForValue().get(LINK_GOTO_KEY + fullShortLink), LinkGotoDO.class);
            if (linkGotoDO != null) {
                redirectToTargetUrl(linkGotoDO.getTargetUrl(), response);
                return;
            }

            // 数据库查询跳转目标地址
            LinkDO linkDO = getValidLinkDO(fullShortLink);

            if (linkDO == null) {
                stringRedisTemplate.opsForValue().set(
                        LINK_GOTO_NULL_KEY + fullShortLink,
                        "-",
                        30,
                        TimeUnit.MINUTES);
                redirectToNotFound(response);
            } else {
                linkGotoDO = LinkGotoDO.builder()
                        .fullShortUrl(fullShortLink)
                        .gid(linkDO.getGid())
                        .targetUrl(linkDO.getOriginUrl())
                        .build();
                stringRedisTemplate.opsForValue().set(
                        LINK_GOTO_KEY + fullShortLink,
                        JSON.toJSONString(linkGotoDO),
                        LinkUtil.getCacheExpireTime(linkDO.getValidDate()),
                        TimeUnit.MILLISECONDS);
                redirectToTargetUrl(linkDO.getOriginUrl(), response);
            }

        } finally {
            linkGotoLock.unlock();
            // 记录访问信息
            if (linkGotoDO != null) {
                LinkAccessInfoDTO linkAccessInfoDTO = buildLinkAccessInfoDTO(fullShortLink, linkGotoDO.getGid(), request, response);
                saveLinkAccessInfo(linkAccessInfoDTO);
            }
        }
    }

    @Override
    public void saveLinkAccessInfo(LinkAccessInfoDTO linkAccessInfoDTO) {
        // 记录总访问信息
        LinkDO linkTotalStatus = LinkDO.builder()
                .fullShortUrl(linkAccessInfoDTO.getFullShortUrl())
                .gid(linkAccessInfoDTO.getGid())
                .totalPv(1)
                .totalUv(Boolean.TRUE.equals(linkAccessInfoDTO.getUvFirstFlag()) ? 1 : 0)
                .totalUip(Boolean.TRUE.equals(linkAccessInfoDTO.getUipFirstFlag()) ? 1 : 0)
                .build();
        linkMapper.recordLinkTotalStatus(linkTotalStatus);

        // 记录今日访问信息
        LinkStatusTodayDO linkStatusTodayDO = LinkStatusTodayDO.builder()
                .fullShortUrl(linkAccessInfoDTO.getFullShortUrl())
                .gid(linkAccessInfoDTO.getGid())
                .accessDate(linkAccessInfoDTO.getAccessDate())
                .todayPv(1)
                .todayUv(Boolean.TRUE.equals(linkAccessInfoDTO.getUvFirstFlag()) ? 1 : 0)
                .todayUip(Boolean.TRUE.equals(linkAccessInfoDTO.getUipFirstFlag()) ? 1 : 0)
                .build();
        linkStatusTodayMapper.recordLinkTodayStatus(linkStatusTodayDO);


        LinkAccessStatusDO linkAccessStatusDO = LinkAccessStatusDO.builder()
                .fullShortUrl(linkAccessInfoDTO.getFullShortUrl())
                .gid(linkAccessInfoDTO.getGid())
                .uv(Boolean.TRUE.equals(linkAccessInfoDTO.getUvFirstFlag()) ? 1 : 0)
                .uip(Boolean.TRUE.equals(linkAccessInfoDTO.getUipFirstFlag()) ? 1 : 0)
                .pv(1)
                .accessDate(linkAccessInfoDTO.getAccessDate())
                .accessWeekday(linkAccessInfoDTO.getAccessWeekday())
                .accessHour(linkAccessInfoDTO.getAccessHour())
                .build();
        linkAccessStatusMapper.recordAccessStatus(linkAccessStatusDO);


        // 记录地区访问信息
        LinkLocaleStatusDO linkLocaleStatusDO = LinkLocaleStatusDO.builder()
                .fullShortUrl(linkAccessInfoDTO.getFullShortUrl())
                .gid(linkAccessInfoDTO.getGid())
                .accessDate(linkAccessInfoDTO.getAccessDate())
                .adcode(linkAccessInfoDTO.getAdcode())
                .country(linkAccessInfoDTO.getCountry())
                .province(linkAccessInfoDTO.getProvince())
                .city(linkAccessInfoDTO.getCity())
                .accessCount(1)
                .build();
        linkLocaleStatusMapper.saveAccessStatus(linkLocaleStatusDO);

        // 记录访问设备信息
        LinkDeviceStatusDO linkDeviceStatusDO = LinkDeviceStatusDO.builder()
                .fullShortUrl(linkAccessInfoDTO.getFullShortUrl())
                .gid(linkAccessInfoDTO.getGid())
                .accessDate(linkAccessInfoDTO.getAccessDate())
                .device(linkAccessInfoDTO.getDevice())
                .accessCount(1)
                .build();
        linkDeviceStatusMapper.saveAccessStatus(linkDeviceStatusDO);

        // 记录访问系统信息
        LinkOsStatusDO linkOsStatusDO = LinkOsStatusDO.builder()
                .fullShortUrl(linkAccessInfoDTO.getFullShortUrl())
                .gid(linkAccessInfoDTO.getGid())
                .accessDate(linkAccessInfoDTO.getAccessDate())
                .operatorSystem(linkAccessInfoDTO.getOs())
                .accessCount(1)
                .build();
        linkOsStatusMapper.saveAccessStatus(linkOsStatusDO);

        // 记录访问网络信息
        LinkNetworkStatusDO linkNetworkStatusDO = LinkNetworkStatusDO.builder()
                .fullShortUrl(linkAccessInfoDTO.getFullShortUrl())
                .gid(linkAccessInfoDTO.getGid())
                .accessDate(linkAccessInfoDTO.getAccessDate())
                .network(linkAccessInfoDTO.getNetwork())
                .accessCount(1)
                .build();
        linkNetworkStatusMapper.saveAccessStatus(linkNetworkStatusDO);

        // 记录访问浏览器信息
        LinkBrowserStatusDO linkBrowserStatusDO = LinkBrowserStatusDO.builder()
                .fullShortUrl(linkAccessInfoDTO.getFullShortUrl())
                .gid(linkAccessInfoDTO.getGid())
                .accessDate(linkAccessInfoDTO.getAccessDate())
                .browser(linkAccessInfoDTO.getBrowser())
                .accessCount(1)
                .build();
        linkBrowserStatusMapper.saveAccessStatus(linkBrowserStatusDO);

        // 记录访问日志信息
        String localeCn = linkAccessInfoDTO.getCountry()
                + "-" + linkAccessInfoDTO.getProvince()
                + "-" + linkAccessInfoDTO.getCity();
        LinkAccessLogDO linkAccessLogDO = LinkAccessLogDO.builder()
                .fullShortUrl(linkAccessInfoDTO.getFullShortUrl())
                .gid(linkAccessInfoDTO.getGid())
                .user(linkAccessInfoDTO.getUvInfo())
                .accessIp(linkAccessInfoDTO.getAccessIP())
                .accessNetwork(linkAccessInfoDTO.getNetwork())
                .accessOs(linkAccessInfoDTO.getOs())
                .accessDevice(linkAccessInfoDTO.getDevice())
                .accessBrowser(linkAccessInfoDTO.getBrowser())
                .accessLocale(localeCn)
                .build();
        linkAccessLogMapper.insert(linkAccessLogDO);
    }

    /**
     * 从数据库查询有效的短链接实体
     *
     * @param fullShortLink 完整短链接
     * @return 有效的短链接实体
     */
    private LinkDO getValidLinkDO(String fullShortLink) {
        LambdaQueryWrapper<LinkGotoDO> linkGotoDOQueryWrapper = Wrappers.lambdaQuery(LinkGotoDO.class)
                .eq(LinkGotoDO::getFullShortUrl, fullShortLink);
        LinkGotoDO linkGotoDO = linkGotoMapper.selectOne(linkGotoDOQueryWrapper);
        if (linkGotoDO == null) {
            return null;
        }

        LambdaQueryWrapper<LinkDO> linkDOQueryWrapper = Wrappers.lambdaQuery(LinkDO.class)
                .eq(LinkDO::getFullShortUrl, linkGotoDO.getFullShortUrl())
                .eq(LinkDO::getGid, linkGotoDO.getGid())
                .eq(LinkDO::getEnableStatus, 0)
                .eq(LinkDO::getDelFlag, 0);
        LinkDO linkDO = linkMapper.selectOne(linkDOQueryWrapper);

        if (linkDO == null || (linkDO.getValidDate() != null && linkDO.getValidDate().isBefore(LocalDateTime.now()))) {
            return null;
        }

        return linkDO;
    }

    /**
     * 重定向到目标网址
     */
    private void redirectToTargetUrl(String targetUrl, HttpServletResponse response) {
        try {
            response.sendRedirect(targetUrl);
        } catch (IOException e) {
            throw new ServiceException("短链接跳转失败");
        }
    }

    /**
     * 重定向到短链接没找到页面
     */
    private void redirectToNotFound(HttpServletResponse response) {
        try {
            response.sendRedirect("/page/notfound");
        } catch (IOException e) {
            throw new ServiceException("短链接跳转失败");
        }
    }

    private String generateShortLinkSuffix(String domain, String originUrl) {
        String linkSuffix;
        String fullShortUrl;
        int generateCount = 0;
        do {
            generateCount++;
            if (generateCount > LINK_GENERATE_MAX_COUNT) {
                throw new ServiceException("短链接生成重复次数过多");
            }
            String origin = originUrl + System.currentTimeMillis();
            linkSuffix = HashUtil.hashToBase62(origin);
            fullShortUrl = domain + "/" + linkSuffix;
        } while (linkGenerateCachePenetration.contains(fullShortUrl));
        return linkSuffix;
    }

    LinkAccessInfoDTO buildLinkAccessInfoDTO(String fullShortLink, String gid, HttpServletRequest request, HttpServletResponse response) {
        LocalDateTime endOfDay = LocalDateTime.now().withHour(23).withMinute(59).withSecond(59).withNano(999999999);
        long secondsUntilEndOfDay = LocalDateTime.now().until(endOfDay, ChronoUnit.SECONDS);
        LocalDate accessDate = LocalDate.now();
        Integer accessWeekday = LocalDate.now().getDayOfWeek().getValue();
        Integer accessHour = LocalDateTime.now().getHour();

        // 获取 uv 信息
        AtomicReference<Boolean> uvFlag = new AtomicReference<>(false);
        AtomicReference<String> uvInfo = new AtomicReference<>();
        Cookie[] cookies = request.getCookies();
        String uvKey = LINK_ACCESS_UV_KEY + fullShortLink + accessDate;
        Arrays.stream(Optional.ofNullable(cookies).orElse(new Cookie[0]))
                .filter(cookie -> cookie != null && CharSequenceUtil.equals(cookie.getName(), "access_flag"))
                .findFirst()
                .ifPresentOrElse(cookie -> {
                    uvInfo.set(cookie.getValue());
                    Long added = stringRedisTemplate.opsForSet().add(uvKey, cookie.getValue());
                    stringRedisTemplate.expire(
                            uvKey,
                            secondsUntilEndOfDay + 10,
                            TimeUnit.SECONDS);
                    uvFlag.set(added != null && added > 0L);
                }, () -> {
                    uvFlag.set(true);
                    String accessUserInfo = UUID.fastUUID().toString();
                    uvInfo.set(accessUserInfo);
                    Cookie cookie = new Cookie("access_flag", accessUserInfo);
                    cookie.setMaxAge(30 * 24 * 60 * 60);
                    cookie.setPath(CharSequenceUtil.subAfter(fullShortLink, "/", true));
                    response.addCookie(cookie);
                    stringRedisTemplate.opsForSet().add(uvKey, accessUserInfo);
                    stringRedisTemplate.expire(
                            uvKey,
                            secondsUntilEndOfDay + 10,
                            TimeUnit.SECONDS);
                });

        boolean uipFlag = false;
        String accessIP = RequestInfoUtil.getIPAddress(request);
        String uipKey = LINK_ACCESS_UIP_KEY + fullShortLink + accessDate;
        Long added = stringRedisTemplate.opsForSet()
                .add(uipKey, accessIP);
        stringRedisTemplate.expire(
                uipKey,
                secondsUntilEndOfDay + 10,
                TimeUnit.SECONDS);
        if (Objects.equals(added, 1L)) {
            uipFlag = true;
        }

        String country = "中国";
        String province = "未知";
        String city = "未知";
        String adcode = "未知";
        Map<String, Object> params = new HashMap<>();
        params.put("key", amapProperties.getAmapKey());
        params.put("ip", accessIP);
        String responseJsonStr = HttpUtil.get("https://restapi.amap.com/v3/ip", params);
        JSONObject localeResult = JSON.parseObject(responseJsonStr);
        if (CharSequenceUtil.equals("10000", localeResult.getString("infocode"))
                && !CharSequenceUtil.equals("[]", localeResult.getString("adcode"))) {
            province = localeResult.getString("province");
            city = localeResult.getString("city");
            adcode = localeResult.getString("adcode");
        }

        // 记录访问设备信息
        String device = RequestInfoUtil.getDevice(request);

        // 记录访问系统信息
        String operatorSystem = RequestInfoUtil.getOperatingSystem(request);

        // 记录访问网络信息
        String network = RequestInfoUtil.getNetwork(request);

        // 记录访问浏览器信息
        String browser = RequestInfoUtil.getBrowser(request);


        return LinkAccessInfoDTO.builder()
                .fullShortUrl(fullShortLink)
                .gid(gid)
                .accessDate(accessDate)
                .accessWeekday(accessWeekday)
                .accessHour(accessHour)
                .uvInfo(uvInfo.get())
                .uvFirstFlag(uvFlag.get())
                .uipFirstFlag(uipFlag)
                .adcode(adcode)
                .province(province)
                .city(city)
                .country(country)
                .device(device)
                .os(operatorSystem)
                .network(network)
                .browser(browser)
                .accessIP(accessIP)
                .build();
    }
}
